//
//  TTVReFixSyntaxHookManager.m
//  TouchTV
//
//  Created by 周启睿 on 2020/1/20.
//  Copyright © 2020 TouchTV. All rights reserved.
//

#import "TTVReFixManager.h"
#import "TTVAspects.h"
#import <CommonCrypto/CommonDigest.h>
#import <objc/runtime.h>
#import <objc/message.h>
#import <JavaScriptCore/JavaScriptCore.h>


static NSString *_regexStr = @"(?<!\\\\)\\.\\s*(\\w+)\\s*\\(";
static NSString *_replaceStr = @".__fn(\"$1\")(";
static NSRegularExpression* _regex;
typedef struct {double d;} TTVDouble;
typedef struct {float f;} TTVFloat;


typedef id (^ttvReplaceFuncBlock)(NSObject* instance,NSInvocation* originalInvocation ,NSArray* arguments);

static NSMethodSignature *fixSignature(NSMethodSignature *signature){
#if TARGET_OS_IPHONE
#ifdef __LP64__
    if (!signature) {
        return nil;
    }
    
    if ([[UIDevice currentDevice].systemVersion floatValue] < 7.09) {
        BOOL isReturnDouble = (strcmp([signature methodReturnType], "d") == 0);
        BOOL isReturnFloat = (strcmp([signature methodReturnType], "f") == 0);

        if (isReturnDouble || isReturnFloat) {
            NSMutableString *types = [NSMutableString stringWithFormat:@"%s@:", isReturnDouble ? @encode(TTVDouble) : @encode(TTVFloat)];
            for (int i = 2; i < signature.numberOfArguments; i++) {
                const char *argType = [signature getArgumentTypeAtIndex:i];
                [types appendFormat:@"%s", argType];
            }
            signature = [NSMethodSignature signatureWithObjCTypes:[types UTF8String]];
        }
    }
#endif
#endif
    return signature;
}

@interface TTVReFixManager ()

@property (nonatomic, strong) JSContext *jsContext;

@property (nonatomic, strong) NSMutableArray<NSString*> *defineList;

@end

@implementation TTVReFixManager

//单例
+ (instancetype)shareManager{
    static TTVReFixManager *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[TTVReFixManager alloc] init];
        manager.defineList = [NSMutableArray array];
    });
    return manager;
}

# pragma mark -

- (void)replaceInstanceOCMethod:(NSString*)className  selectorMethod:(NSString*)selectorMethod isClassMethod:(BOOL)isClassMethod block:(ttvReplaceFuncBlock)block{
    
    
    Class aClass =  isClassMethod ? object_getClass(NSClassFromString(className)) : NSClassFromString(className);
    [aClass ttv_aspect_hookSelector:NSSelectorFromString(selectorMethod)
                                          withOptions:TTVAspectPositionInstead
                                           usingBlock:^(id<TTVAspectInfo> info){
        if (block == nil) return ;
        
        NSObject* instance = [info instance];
        NSArray* arguments = [info arguments];
        JSValue* retObject = block(instance,info.originalInvocation,arguments);
        if (retObject.toObject == nil) return;
        
        NSMethodSignature* signature = info.originalInvocation.methodSignature;
        NSInvocation* invocation = info.originalInvocation;
        const char *argType = [signature methodReturnType];
        switch (argType[0]) {
#define fix_return(_typeChar, _type,_func) \
                case _typeChar: {   \
                    NSNumber* num = retObject.toNumber;\
                    _type arg = [num _func];  \
                    [invocation setReturnValue:&arg];    \
                    break;  \
                }
                fix_return('c', char,charValue)
                fix_return('C', unsigned char,unsignedCharValue)
                fix_return('s', short,shortValue)
                fix_return('S', unsigned short,unsignedShortValue)
                fix_return('i', int,intValue)
                fix_return('I', unsigned int,unsignedIntValue)
                fix_return('l', long,longValue)
                fix_return('L', unsigned long,unsignedLongValue)
                fix_return('q', long long,longLongValue)
                fix_return('Q', unsigned long long,unsignedLongValue)
                fix_return('f', float,floatValue)
                fix_return('d', double,doubleValue)
                fix_return('B', BOOL,boolValue)
            default:{
                id obj = retObject.toObject;
                [info.originalInvocation setReturnValue:&obj];
                break;
            }
        }
    } error:NULL];
}

- (void)analysisReplaceOCMethod:(NSString*)jsFunctionIdentifier callText:(NSString*)callText argCount:(NSInteger)argCount isClassMethod:(BOOL)isClassMethod{
    NSArray* list = [jsFunctionIdentifier componentsSeparatedByString:@"_"];
    NSString* className = list.firstObject;
    NSString* selectorMethod = @"";
    for (int i = 0; i < list.count; i++) {
        if (i > 0) {
            selectorMethod = [selectorMethod stringByAppendingFormat:@"%@:",list[i]];
        }
    }
    
    if (argCount < 2 && [selectorMethod hasSuffix:@":"]) {
        selectorMethod = [selectorMethod substringToIndex:selectorMethod.length - 1];
    }
    
    [self replaceInstanceOCMethod:className selectorMethod:selectorMethod isClassMethod:isClassMethod block:^id(NSObject *instance, NSInvocation *originalInvocation, NSArray *arguments) {
        
        NSMutableArray* args = [NSMutableArray array];
        [args addObject:instance];
        [args addObjectsFromArray:arguments];
        
        NSMutableArray* ary = [NSMutableArray array];
        [ary addObject:jsFunctionIdentifier];
        [ary addObject:instance];
        [ary addObject:originalInvocation];
        [ary addObjectsFromArray:@[args]];
        return [self.jsContext[callText] callWithArguments:ary];
    }];
}

- (id)callSelector:(NSString *)className selectorName:(NSString *)selectorName arguments:(JSValue *)arguments instance:(JSValue *)instance isSuper:(BOOL)isSuper{
    SEL selector = NSSelectorFromString(selectorName);
    @try {
            if ([[instance toObject] isKindOfClass:NSObject.class]) {
            NSObject* instanceObj = instance.toObject;
            
            if ([instanceObj respondsToSelector:selector]) {
                
                NSMethodSignature *signature = [[instanceObj class] instanceMethodSignatureForSelector:selector];
                fixSignature(signature);
                NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
                invocation.target = instanceObj;
                invocation.selector = selector;
                id obj = [self invocation:invocation arguments:arguments.toObject signature:signature];
                return obj;
            }
        }
        else if (className.length > 0){
            Class cls = NSClassFromString(className);
            if (cls) {
                NSMethodSignature* signature = [cls methodSignatureForSelector:selector];
                fixSignature(signature);
                NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];
                invocation.target = cls;
                invocation.selector = selector;
                id obj = [self invocation:invocation arguments:arguments.toObject signature:signature];
                return obj;
            }
        }
    } @catch (NSException *exception) {
        //DLog(@"%@:%@",className,selectorName);
    } @finally {
        
    }

    return nil;
}


/// 调用函数
/// @param invocation invocation description
/// @param objects objects description
/// @param signature signature description
- (id)invocation:(NSInvocation*)invocation arguments:(NSArray*)objects signature:(NSMethodSignature*)signature{
    
@synchronized (self.jsContext) {
    // 设置参数
    NSInteger paramsCount = signature.numberOfArguments - 2; // 除self、_cmd以外的参数个数
    paramsCount = MIN(paramsCount, objects.count);
    for (NSInteger i = 0; i < paramsCount; i++) {
        NSInteger cur = i + 2;
        id object = objects[i];
        if ([object isKindOfClass:[NSNull class]]) continue;
        const char *argType = [signature getArgumentTypeAtIndex:cur];
        
        switch (argType[0]) {
#define fix_arguments_CASE(_typeChar, _type,_func) \
            case _typeChar: {   \
                NSNumber* num = object;\
                _type arg = [num _func];  \
                [invocation setArgument:&arg atIndex:cur];    \
                break;  \
            }
            fix_arguments_CASE('c', char,charValue)
            fix_arguments_CASE('C', unsigned char,unsignedCharValue)
            fix_arguments_CASE('s', short,shortValue)
            fix_arguments_CASE('S', unsigned short,unsignedShortValue)
            fix_arguments_CASE('i', int,intValue)
            fix_arguments_CASE('I', unsigned int,unsignedIntValue)
            fix_arguments_CASE('l', long,longValue)
            fix_arguments_CASE('L', unsigned long,unsignedLongValue)
            fix_arguments_CASE('q', long long,longLongValue)
            fix_arguments_CASE('Q', unsigned long long,unsignedLongValue)
            fix_arguments_CASE('f', float,floatValue)
            fix_arguments_CASE('d', double,doubleValue)
            fix_arguments_CASE('B', BOOL,boolValue)
            default:{
                [invocation setArgument:&object atIndex:cur];
                break;
            }
                
        }
        
    }
    return [self invoke:invocation];
}
    return nil;
}

/// 调用方便并修正返回值
/// @param invocation invocation description
- (id)invoke:(NSInvocation*)invocation{
@synchronized (self.jsContext) {
    [invocation invoke];
        
    id __unsafe_unretained returnValue = nil;

    const char *argType = [invocation.methodSignature methodReturnType];
    switch (argType[0]) {
    #define back_case(_typeString, _type) \
            case _typeString: { \
                _type tempResultSet; \
                [invocation getReturnValue:&tempResultSet];\
                returnValue = @(tempResultSet); \
                break; \
                }
                
            back_case('c', char)
            back_case('C', unsigned char)
            back_case('s', short)
            back_case('S', unsigned short)
            back_case('i', int)
            back_case('I', unsigned int)
            back_case('l', long)
            back_case('L', unsigned long)
            back_case('q', long long)
            back_case('Q', unsigned long long)
            back_case('f', float)
            //back_case('d', double)
            back_case('B', BOOL)
            case 'd':{
                double tempResultSet;
                [invocation getReturnValue:&tempResultSet];
                returnValue = @(tempResultSet);
                break;
            }
            default:{
                if (invocation.methodSignature.methodReturnLength) {
                    //获取返回值
                    [invocation getReturnValue:&returnValue];
                }
                break;
            }
        }
        return returnValue;
    }
    return nil;
}

- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL{
    if (!script || ![JSContext class]) {

        return nil;
    }
    if (script.length == 0) return nil;
    
    if (!_regex) {
        _regex = [NSRegularExpression regularExpressionWithPattern:_regexStr options:0 error:nil];
    }
    NSString *formatedScript = [NSString stringWithFormat:@";(function(){try{\n%@\n}catch(e){_OC_catch(e.message, e.stack)}})();", [_regex stringByReplacingMatchesInString:script options:0 range:NSMakeRange(0, script.length) withTemplate:_replaceStr]];
    
    @try {
        if ([self.jsContext respondsToSelector:@selector(evaluateScript:withSourceURL:)]) {
            return [self.jsContext evaluateScript:formatedScript withSourceURL:resourceURL];
        } else {
            return [self.jsContext evaluateScript:formatedScript];
        }
    }
    @catch (NSException *exception) {
        //_exceptionBlock([NSString stringWithFormat:@"%@", exception]);
    }
    return nil;
}

# pragma mark -
/**
 获取文件所在name，默认情况下podName和bundlename相同，传一个即可
 
 @param bundleName bundle名字，就是在resource_bundles里面的名字
 @param podName pod的名字
 @return bundle
 */
+ (NSBundle *)bundleWithBundleName:(NSString *)bundleName podName:(NSString *)podName{
    if (bundleName == nil && podName == nil) {
        @throw @"bundleName和podName不能同时为空";
    }else if (bundleName == nil ) {
        bundleName = podName;
    }else if (podName == nil) {
        podName = bundleName;
    }
    
    
    if ([bundleName containsString:@".bundle"]) {
        bundleName = [bundleName componentsSeparatedByString:@".bundle"].firstObject;
    }
    //没使用framwork的情况下
    NSURL *associateBundleURL = [[NSBundle mainBundle] URLForResource:bundleName withExtension:@"bundle"];
    //使用framework形式
    if (!associateBundleURL) {
        associateBundleURL = [[NSBundle mainBundle] URLForResource:@"Frameworks" withExtension:nil];
        associateBundleURL = [associateBundleURL URLByAppendingPathComponent:podName];
        associateBundleURL = [associateBundleURL URLByAppendingPathExtension:@"framework"];
        NSBundle *associateBunle = [NSBundle bundleWithURL:associateBundleURL];
        associateBundleURL = [associateBunle URLForResource:bundleName withExtension:@"bundle"];
    }
    
    NSAssert(associateBundleURL, @"取不到关联bundle");
    //生产环境直接返回空
    return associateBundleURL?[NSBundle bundleWithURL:associateBundleURL]:nil;
}

- (void)start:(NSString*)jsPath isLocal:(BOOL)isLocal{
        __weak TTVReFixManager *weakSelf = self;
        
        self.jsContext = [[JSContext alloc]init];
        self.jsContext[@"_nature_defineClass"] = ^(NSString *callText,NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods,JSValue *argLenth) {
            NSInteger argCount = argLenth.toInt32;
            if (instanceMethods.toDictionary) {
                [weakSelf.defineList addObject:classDeclaration];
                [weakSelf analysisReplaceOCMethod:classDeclaration callText:callText argCount:argCount isClassMethod:false];
            }
            else if (classMethods.toDictionary){
                [weakSelf.defineList addObject:classDeclaration];
                [weakSelf analysisReplaceOCMethod:classDeclaration callText:callText argCount:argCount isClassMethod:true];
            }
        };
        
        self.jsContext[@"_nature_catch"] = ^ (NSString *error, NSString *reson) {
            NSLog(@"%@_%@",error,reson);
        };
        
        self.jsContext[@"_nature_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) {
            return [weakSelf callSelector:nil selectorName:selectorName arguments:arguments instance:obj isSuper:isSuper];
        };
        self.jsContext[@"_nature_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) {
            return  [weakSelf callSelector:className selectorName:selectorName arguments:arguments instance:nil isSuper:NO];
        };
        
        self.jsContext[@"_nature_orgFunc"] = ^id(JSValue *orgFunc, JSValue *arguments) {
            if ([orgFunc.toObject isKindOfClass:NSInvocation.class]) {
                return [weakSelf invoke:orgFunc.toObject];
            }
            return nil;
        };
        
        //引擎js
        
        NSBundle* resBundle =  [[self class] bundleWithBundleName:@"TTVReFix" podName:nil];
        NSString *path = [resBundle pathForResource:@"TTVRefixJsCore" ofType:@"js"];
        NSString *jsCore = [[NSString alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:path] encoding:NSUTF8StringEncoding];
        [self.jsContext evaluateScript:jsCore withSourceURL:[NSURL URLWithString:@"TTVRefix.js"]];
        
        //修复js
        NSString *jsCoreFix = [[NSString alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:jsPath] encoding:NSUTF8StringEncoding];
        [self evaluateScript:jsCoreFix withSourceURL:[NSURL URLWithString:@"jsCoreFix.js"]];
        
//        if (isLocal) {
//           //修复文件
//            NSString *pathDemo = [[NSBundle bundleForClass:[self class]] pathForResource:@"TTVRefixDemo" ofType:@"js"];
//            NSString* pathDemoText = jsPath.length == 0 ? pathDemo : jsPath;
//            NSString *jsCoreDemo = [[NSString alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:pathDemoText] encoding:NSUTF8StringEncoding];
//            [self evaluateScript:jsCoreDemo withSourceURL:[NSURL URLWithString:@"jsCoreDemo.js"]];
//        }
}

- (NSString *)md5:(NSString *)str{
    const char *cStr = [str UTF8String];
    unsigned char digest[CC_MD5_DIGEST_LENGTH];
    CC_MD5(cStr,(CC_LONG)strlen(cStr), digest);
    NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
    for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
    [output appendFormat:@"%02x", digest[i]];
    return  output;
}

- (void)downloadJs:(NSString*)jsHttpUrl block:(void (^)(NSString* finishPath))block{
    
    //校验
    if (block == nil) return;
    if (jsHttpUrl.length == 0) {
        block(nil);
        return;
    }
    
    //路径
    NSArray  *pathes     = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES);
    NSString *md5        = [self md5:jsHttpUrl];
    NSString *path       = [pathes objectAtIndex:0];
    NSURL    *url        = [NSURL URLWithString:jsHttpUrl];
    NSString *dirPath    = [NSString stringWithFormat:@"%@/js_refix/",path];
    NSString *finishPath = [NSString stringWithFormat:@"%@%@.js",dirPath,md5];
    
    //文件夹
    NSError *createdError = nil;
    BOOL isCreatedDir = [[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&createdError];
    if (isCreatedDir == false) {
        block(nil);
        return;
    }
    
    //检测文件是否存在
    BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:finishPath];
    if (!isExist) {
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
        dispatch_async(queue, ^{
            NSError *error = nil;
            NSData *data = [NSData dataWithContentsOfURL:url options:0 error:&error];
            if(error)
            {
                block(nil);
            }
            else{
                [data writeToFile:finishPath options:0 error:&error];
                if (error) {
                    block(nil);
                }
                else{
                    block(finishPath);
                }
            }
        });
    }
    else{
        block(finishPath);
    }
}

# pragma mark -
- (void)start:(NSString*)localJsPath{
    [self start:localJsPath isLocal:false];
}

- (void)run:(NSString*)httpUrl{
    //[[self downloadJs:@"https://gz-cos-1252598787.cos.ap-guangzhou.myqcloud.com/xxx/main_mj0_1.0.0.js"] subscribeNext:^(NSString* x) {
    __weak __typeof(self)weakSelf = self;
    [self downloadJs:httpUrl block:^(NSString *finishPath) {
        [weakSelf start:finishPath];
    }];
}


@end
